﻿// -----------------------------------------------------------------------
// <copyright file="Communicator.cs" company="SoftServe">
// Comunicator for Web server
// </copyright>
// -----------------------------------------------------------------------


namespace WebServer.BusinessLogic.Communication
{
    using WebServer.BusinessLogic.Wrappers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using WebServer.BusinessLogic.Handlers;
    using WebServer.BusinessLogic.Logs;

    /// <summary>
    /// Comunicator class
    /// </summary>
    public class Communicator : INotifyPropertyChanged, ICommunicator
    {
        /// <summary>
        /// Is used to make changes to volative variables in Communicator
        /// </summary>
        private static object _syncRoot = new object();

        /// <summary>
        /// List of hanlders that should be set through Constructor
        /// </summary>
        private List<Handler> handlers;

        /// <summary>
        /// Field for load balancer
        /// </summary>
        private LoadBalancer loadBalancer;

        /// <summary>
        /// Number of active connections
        /// </summary>
        private int activeConnections;

        /// <summary>
        /// Tcp listener instanse
        /// </summary>
        private TcpListener tcpListener;

        /// <summary>
        /// Configurator that has to be used in this class. Inject through constructor of Communicator
        /// </summary>
        private Configurator _configurator;

        /// <summary>
        /// Initializes a new instance of the <see cref="Communicator" /> class.
        /// </summary>
        /// <param name="conf"></param>
        public Communicator(Configurator conf = null)
        {
            if (conf == null)
            {
                conf = Configurator.Instance;
            }

            // injecting configurator
            _configurator = conf;

            this.CommunicatorInitialize();
            this.handlers = this.CreateDefaultHandlers();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Communicator" /> class.
        /// Creates communicator with custom handlers. Could be used for Mocking
        /// </summary>
        /// <param name="handlers">List of Handlers, sorted in order of processing</param>
        public Communicator(List<Handler> handlers)
        {
            this.CommunicatorInitialize();

            this.handlers = handlers;
        }

        /// <summary>
        /// event Property Changed
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Gets Loadbalancer
        /// </summary>
        public LoadBalancer LoadBalancer
        {
            get
            {
                return this.loadBalancer;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether	Bool for starting listening
        /// </summary>
        public bool IsActive { get; set; }

        /// <summary>
        /// Gets number of active connections
        /// </summary>
        public int ActiveConnections
        {
            get
            {
                return this.activeConnections;
            }

            private set
            {
                this.activeConnections = value;
                this.OnPropertyChanged("ActiveConnections");
            }
        }


        /// <summary>
        /// Starts listening.
        /// </summary>
        public void StartListening()
        {
            // Set thread max threads.
            int maxThreads = Environment.ProcessorCount * 10;
            ThreadPool.SetMaxThreads(maxThreads, maxThreads);

            // set the active state
            this.IsActive = true;

            // run thread of webserver
            Thread thread = new Thread(this.StartListeningThread);
            thread.Name = "Communicator Thread";
            thread.Start();

            this.loadBalancer.StartAnsweringThread();

            if (_configurator.ServerMode == ServerConfiguration.LoadBalancer)
            {
                this.loadBalancer.StartAskingThread();
            }
        }

        /// <summary>
        /// Stops listening in the while loop
        /// </summary>
        public void StopListening()
        {
            this.IsActive = false;

            if (this.tcpListener != null)
            {
                this.tcpListener.Server.Close();
                this.tcpListener.Stop();
            }
            
            // stop LoadBalancer
            this.loadBalancer.StopAnsweringThread();
            this.loadBalancer.StopAskingThread();
        }

        /// <summary>
        /// getting percent of load
        /// </summary>
        /// <returns>percent of load</returns>
        public int GetLoad()
        {
            return this.ActiveConnections * 100 / _configurator.MaxUsers;
        }

        /// <summary>
        /// Procces changing property
        /// </summary>
        /// <param name="propertyName">name of property</param>
        protected void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// trying to free port for webserver
        /// </summary>
        /// <returns>success of trying</returns>
        public bool TryToFreePortForWebserver()
        {
            this.tcpListener = new TcpListener(_configurator.Host, _configurator.Port);
            try
            {
                this.tcpListener.Start();
                return true;
            }
            catch (SocketException)
            {
                Logger.Instance.Log(string.Format("Error. Server could not restart itself on address: {0}:{1}", _configurator.Host, _configurator.Port));
            }
            finally
            {
                this.tcpListener.Server.Close();
            }

            return false;
        }

        /// <summary>
        /// Starts listening thread
        /// </summary>
        private void StartListeningThread()
        {
            for (int i = _configurator.Port; i < 65000; i++)
            {
                if (this.TryToFreePortForWebserver())
                {
                    // port free'd
                    break;
                }

                // try to change port
                _configurator.Port++;
            }

            // create new tcp listener
            this.tcpListener = new TcpListener(_configurator.Host, _configurator.Port);

            // start listening for incomming connections
            this.tcpListener.Start();

            while (this.IsActive)
            {
                try
                {
                    Socket tcpSocket = this.tcpListener.AcceptSocket();
                    if (tcpSocket.Connected)
                    {
                        ThreadPool.QueueUserWorkItem(this.ReturnResponseThreadProc, tcpSocket);
                    }
                }
                catch (SocketException e)
                {
                    Logger.Instance.Log("Error while AcceptSocket: " + e.Message);
                }
                catch (ObjectDisposedException e)
                {
                    Logger.Instance.Log("UNKNOWN Error in AcceptingSocket: " + e.Message);
                }
                catch(InvalidOperationException)
                {
                    Logger.Instance.Log("Restart failed");
                }
            }

            Trace.WriteLine("Stopping thread " + Thread.CurrentThread.GetHashCode());
        }

        /// <summary>
        /// Return Response Thread Proc
        /// </summary>
        /// <param name="tcpSocket">tcpSocket object</param>
        private void ReturnResponseThreadProc(object tcpSocket)
        {
            //Stopwatch sw = new Stopwatch();
            //sw.Reset();
            //sw.Start();

            lock (_syncRoot)
            {
                ActiveConnections++;
            }

            // create socket wrapper that contains real socket and implements ISocketWrapper interface
            var socketWrapper = new SocketWrapper((Socket) tcpSocket);

            // create socket manager
            Response response = null;
            using (var socketManager = new SocketManager(socketWrapper))
            {
                // process socket request
                Request request = socketManager.GetRequestFromSocket();

                if (request != null && request.IsCorrect)
                {
                    // handle request here, in communicator
                    response = HandleRequest(request);
                }
                socketManager.SendResponse(response);
            }

            

            // decrement active connections
            lock (_syncRoot)
            {
                ActiveConnections--;
            }

            //sw.Stop();
            //Trace.WriteLine(string.Format("Took {0} ms", sw.ElapsedMilliseconds));
        }

        

        /// <summary>
        /// Initialize default variables
        /// </summary>
        private void CommunicatorInitialize()
        {
            this.loadBalancer = new LoadBalancer(this);
        }

        

        

        /// <summary>
        /// Creates list of default handlers for webserver. Just add your new handers here.
        /// </summary>
        /// <returns>List of abstract Hander classes</returns>
        private List<Handler> CreateDefaultHandlers()
        {
            return new List<Handler>
                               {
                                   new LoadBalancerHandler(),
                                   new SessionHandler(),
                                   new DirectoryHandler(),
                                   new AdminHandler(),
                                   new TextHandler(),
                                   new BinaryHandler(),
                                   new RedirectHandler(),
                                   new ErrorHandler()
                               };
        }

        /// <summary>
        /// Sends formed request object to the handlers
        /// </summary>
        /// <param name="request">request object</param>
        /// <returns>Responce object</returns>
        public Response HandleRequest(Request request)
        {
            if (this.handlers.Count > 0)
            {
                for (int i = 0; i < this.handlers.Count - 1; i++)
                {
                    // set Handler sequence of execution
                    this.handlers[i].SetNext(this.handlers[i + 1]);
                }

                // fire first Handler
                return this.handlers[0].Handle(request);
            }
            
            throw new Exception("No handlers were set for Communicator. This behaviour is not allowed.");
        }

        /// <summary>
        /// Gets page
        /// </summary>
        /// <param name="remoteServer">remoteServer adress</param>
        /// <returns>Response object</returns>
        public static Response GetPage(string remoteServer, IHttpWebRequestWrapper webRequest)
        {
            var ourResponse = new Response();
            ourResponse.HttpResponseCode = (int) HttpStatusCode.InternalServerError;

            string host = !remoteServer.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? "http://" + remoteServer : remoteServer;

            Uri uri;
            if (Uri.TryCreate(host, UriKind.Absolute, out uri))
            {
                webRequest.Create(uri);
                //HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(uri);
                IHttpWebResponseWrapper response;
                var data = new byte[0];
                try
                {
                    response = webRequest.GetResponse();
                    if (response.ContentLength > 0)
                    {
                        data = new byte[response.ContentLength];
                        using (var stream = response.GetResponseStream())
                        {
                            if (stream != null)
                            {
                                stream.Read(data, 0, data.Length);
                            }
                        }
                    }
                }
                catch (WebException)
                {
                    // file not found here
                    response = null;
                }

                if (response != null)
                {
                    ourResponse = new Response(response.ContentType, response.ProtocolVersion.ToString(), data);
                    ourResponse.HttpResponseCode = (int)response.StatusCode;
                    ourResponse.HttpResponseCodeDescription = response.StatusDescription;
                }
            }

            return ourResponse;
        }

        /// <summary>
        /// Gets page
        /// </summary>
        /// <param name="remoteServer">remoteServer adress</param>
        /// <returns>Response object</returns>
        public static Response GetPage(string remoteServer)
        {
            return GetPage(remoteServer, new HttpWebRequestWrapper());
        }
    }
}
